package com.tyk.bookstore.front.member.service.impl;

import com.tyk.bookstore.front.common.core.model.enume.PrincipalType;
import com.tyk.bookstore.front.common.core.model.exception.CustomException;
import com.tyk.bookstore.front.common.core.model.result.CommonResultEnum;
import com.tyk.bookstore.front.common.core.util.AssertUtil;
import com.tyk.bookstore.front.common.core.util.ComUtil;
import com.tyk.bookstore.front.common.core.util.ObjUtil;
import com.tyk.bookstore.front.common.core.util.ParamUtil;
import com.tyk.bookstore.front.common.dal.mapper.dto.SinDto;
import com.tyk.bookstore.front.common.other.code.behavior.manager.BehaviorCodeManager;
import com.tyk.bookstore.front.common.other.code.behavior.model.enume.SceneType;
import com.tyk.bookstore.front.common.other.code.msg.manager.MsgCodeManager;
import com.tyk.bookstore.front.common.other.code.msg.model.enume.MsgCodeType;
import com.tyk.bookstore.front.common.security.util.PasswordUtil;
import com.tyk.bookstore.front.member.constant.MemberConstant;
import com.tyk.bookstore.front.member.manager.MemberRedisManager;
import com.tyk.bookstore.front.member.model.entity.MemberDetailDo;
import com.tyk.bookstore.front.member.model.entity.MemberDo;
import com.tyk.bookstore.front.member.model.query.MemberRegisterQuery;
import com.tyk.bookstore.front.member.model.query.MsgCodeCheckQuery;
import com.tyk.bookstore.front.member.model.query.MsgCodeSendWithBehaviorCheckQuery;
import com.tyk.bookstore.front.member.model.query.PwdFindQuery;
import com.tyk.bookstore.front.member.model.result.MemberResultEnum;
import com.tyk.bookstore.front.member.model.vo.MemberRegisterVo;
import com.tyk.bookstore.front.member.repository.MemberDetailRepository;
import com.tyk.bookstore.front.member.repository.MemberRepository;
import com.tyk.bookstore.front.member.service.MemberService;
import com.tyk.bookstore.front.member.util.MemberUtil;
import com.tyk.bookstore.front.system.service.SystemSettingApiService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class MemberServiceImpl implements MemberService {

    private final MemberRepository memberRepository;
    private final MemberDetailRepository memberDetailRepository;
    private final MemberRedisManager memberRedisManager;
    private final SystemSettingApiService systemSettingApiService;
    private final BehaviorCodeManager behaviorCodeManager;
    private final MsgCodeManager msgCodeManager;

    @Override
    public String sendMsgCodeWithBehaviorCheck(
            MsgCodeSendWithBehaviorCheckQuery query,
            SceneType scene
    ) {

        // 校验行为验证码
        boolean check = behaviorCodeManager.check(scene, query.getVerifyParam());
        AssertUtil.isTrue(check, CommonResultEnum.CODE_BEHAVIOR_ERROR);

        // 获取手机号
        String phone = principalToPhone(query.getPrincipal(), scene);

        // 发送验证码
        return sendPhoneCode(phone, scene);
    }


    @Override
    public String checkMsgCode(MsgCodeCheckQuery query, SceneType scene) {

        // 获取校验手机号
        String phone = principalToPhone(query.getPrincipal(), scene);

        // 校验消息验证码
        String code = memberRedisManager.getMsgCode(scene, query.getVerifyCode(), phone);
        AssertUtil.isTrue(
                ObjUtil.equal(code, query.getCertificate()),
                CommonResultEnum.CODE_MSG_PHONE_ERROR_OR_EXPIRED
        );

        // 删除消息验证码
        memberRedisManager.deleteMsgCode(scene, query.getVerifyCode(), phone);

        // 生成消息验证码校验通过凭证
        String certificate = ComUtil.simUuid();
        memberRedisManager.cacheMsgPassCertificate(scene, certificate, phone);

        // 返回验证码校验通过凭证
        return certificate;

    }

@Override
public MemberRegisterVo register(MemberRegisterQuery query) {

    // 校验消息验证码校验通过凭证
    String phone = memberRedisManager.getMsgPassCertificate(SceneType.REGISTER, query.getVerifyCode(), query.getPrincipal());
    AssertUtil.isTrue(
            ObjUtil.equal(phone, query.getPrincipal()),
            CommonResultEnum.ENVIRONMENT_ABNORMAL
    );

    // 检查手机号是否注册
    assertNotRegistered(phone);

    // 分配账号
    String account = MemberUtil.account(
            ac -> memberRepository.exist1Eq(MemberDo::getAccount, ac, MemberDo.class)
    );
    AssertUtil.notBlank(account, CommonResultEnum.NETWORK_ERROR);

    // 默认头像
    // String avatar = systemSettingApiService.getMemberDefaultAvatar();
    String avatar = MemberConstant.DEFAULT_AVATAR;

    // 添加到数据库
    MemberDo member = new MemberDo()
            .setAccount(account)
            .setPhone(phone)
            .setPassword(PasswordUtil.encrypt(query.getPassword()));
    memberRepository.insert(member);
    MemberDetailDo memberDetailDo = new MemberDetailDo()
            .setMemberId(member.getId())
            .setNickname(query.getNickname())
            .setAvatar(avatar);
    memberDetailRepository.insert(memberDetailDo);

    // 返回昵称、账号
    return new MemberRegisterVo(query.getNickname(), account);
}

    @Override
    public void findPwd(PwdFindQuery query, SceneType type) {

        // 校验消息验证码校验通过凭证
        String phone = memberRedisManager.getMsgPassCertificate(type, query.getVerifyCode(), query.getPrincipal());
        AssertUtil.equal(phone, query.getPrincipal(), CommonResultEnum.ENVIRONMENT_ABNORMAL);

        // 重设密码
        memberRepository.update1Set1Eq(
                MemberDo::getPassword, PasswordUtil.encrypt(query.getPassword()),
                MemberDo::getPhone, phone,
                MemberDo.class
        );

    }


    /**
     * 发送手机验证码并缓存
     *
     * @param phone 手机号
     * @param scene 场景
     */
    private String sendPhoneCode(String phone, SceneType scene) {

        // 发送验证码
        String code = msgCodeManager.sendCodeAsync(MsgCodeType.PHONE_ALI, phone);

        // 生成并缓存消息验证码凭证
        String certificate = ComUtil.simUuid();
        memberRedisManager.cacheMsgCode(scene, certificate, phone, code);

        // 返回消息验证码凭证
        return certificate;

    }

    /**
     * 主体=>手机号
     *
     * @param principal 主体
     * @param scene     场景
     * @return 手机号
     */
    private String principalToPhone(String principal, SceneType scene) {

        // 参数校验：校验主体
        PrincipalType type = ParamUtil.of(principal);
        AssertUtil.notNull(type, CommonResultEnum.PARAM_ERROR);

        // 获取手机号
        return switch (scene) {
            case REGISTER -> {
                assertNotRegistered(principal);
                yield principal;
            }
            case FIND_PWD -> switch (type) {
                case ACCOUNT -> {
                    SinDto<String> dto = memberRepository.selectSin1Eq(
                            MemberDo::getPhone,
                            MemberDo::getAccount, principal,
                            MemberDo.class
                    );
                    AssertUtil.notNull(dto, MemberResultEnum.ACCOUNT_NOT_FOUND);
                    yield dto.getV1();
                }
                case EMAIL -> {
                    SinDto<String> dto = memberRepository.selectSin1Eq(
                            MemberDo::getPhone,
                            MemberDo::getEmail, principal,
                            MemberDo.class
                    );
                    AssertUtil.notNull(dto, MemberResultEnum.EMAIL_NOT_BOUND);
                    yield dto.getV1();
                }
                case PHONE -> {
                    SinDto<String> dto = memberRepository.selectSin1Eq(
                            MemberDo::getPhone,
                            MemberDo::getPhone, principal,
                            MemberDo.class
                    );
                    AssertUtil.notNull(dto, MemberResultEnum.PHONE_NOT_REGISTERED);
                    yield dto.getV1();
                }
            };
            default -> throw new CustomException(CommonResultEnum.PARAM_ERROR);
        };
    }


    /**
     * 校验手机号是否注册
     *
     * @param phone 手机号
     */
    private void assertNotRegistered(String phone) {
        AssertUtil.isTrue(ParamUtil.checkPhone(phone), CommonResultEnum.PARAM_ERROR);
        Boolean f = memberRepository.exist1Eq(
                MemberDo::getPhone, phone, MemberDo.class
        );
        AssertUtil.isFalse(f, MemberResultEnum.PHONE_HAS_REGISTERED);
    }

}
